﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using MyBasicLib.Extensions;
using MyBasicLib.Linq.Expressions;
using MyBasicLib.Reflection;
using MyBasicLibLinqExpressions = MyBasicLib.Linq.Expressions;
namespace MyBasicLib.Data.ORMLiteNew.Translation
{
    public static class Expressions
    {
        private static readonly MethodInfo ArrayIndexMethodInfo = typeof (IList).GetProperty("Item").GetGetMethod();

        public static object Eval(this Expression exp)
        {
            if (exp == null) return null;
            exp = exp.TrimUnary();
            if (exp.NodeType == ExpressionType.Constant)
            {
                return ((ConstantExpression) exp).Value;
            }
            if (exp.NodeType == ExpressionType.MemberAccess)
            {
                var memEp = exp as MemberExpression;
                MemberInfo mi = memEp.Member;
                object owner = Eval(memEp.Expression);
                var pi = mi as PropertyInfo;
                if (pi != null)
                {
                    return pi.FastGetValue(owner);
                }
                return (mi as FieldInfo).FastGetValue(owner);
            }
            if (exp.NodeType == ExpressionType.Call)
            {
                var callEp = exp as MethodCallExpression;
                MethodInfo mi = callEp.Method;
                object[] args = callEp.Arguments.Select(p => p.Eval()).ToArray();
                return mi.FastInvoke(callEp.Object.Eval(), args);
                //if (callEp.Arguments.Count == 0)
                //    return mi.FastInvoke(callEp.Object.Eval());
                //if (callEp.Arguments.Count == 1)
                //    return mi.FastInvoke(callEp.Object.Eval(),callEp.Arguments[0].Eval());
            }
            if (exp.NodeType == ExpressionType.ArrayIndex)
            {
                var smp = exp as BinaryExpression;
                object arr = smp.Left.Eval();
                object idx = smp.Right.Eval();
                return ArrayIndexMethodInfo.FastInvoke(arr, idx);
            }
            throw new NotSupportedException();
            //LambdaExpression lambda = Expression.Lambda(exp);
            //Delegate fn = lambda.Compile();

            //return fn.DynamicInvoke(null);
        }
    }


    /// <summary>
    /// Performs bottom-up analysis to determine which nodes can possibly
    /// be part of an evaluated sub-tree.
    /// </summary>
    public class Nominator :MyBasicLibLinqExpressions. ExpressionVisitor
    {
        private readonly Func<Expression, bool> m_fnCanBeEvaluated;
        private HashSet<Expression> m_candidates;
        private bool m_cannotBeEvaluated;

        public Nominator()
            : this(CanBeEvaluatedLocally)
        {
        }

        public Nominator(Func<Expression, bool> fnCanBeEvaluated)
        {
            m_fnCanBeEvaluated = fnCanBeEvaluated ?? CanBeEvaluatedLocally;
        }

        private static bool CanBeEvaluatedLocally(Expression exp)
        {
            return exp.NodeType != ExpressionType.Parameter &&
                   exp.NodeType != ExpressionType.MemberInit && exp.NodeType != ExpressionType.New;
        }

        internal HashSet<Expression> Nominate(Expression expression)
        {
            m_candidates = new HashSet<Expression>();
            Visit(expression);
            return m_candidates;
        }

        public override Expression Visit(Expression expression)
        {
            if (expression != null)
            {
                bool saveCannotBeEvaluated = m_cannotBeEvaluated;
                m_cannotBeEvaluated = false;

                base.Visit(expression);

                if (!m_cannotBeEvaluated)
                {
                    if (m_fnCanBeEvaluated(expression))
                    {
                        if (expression.NodeType != ExpressionType.Constant)
                            m_candidates.Add(expression);
                    }
                    else
                    {
                        m_cannotBeEvaluated = true;
                    }
                }

                m_cannotBeEvaluated |= saveCannotBeEvaluated;
            }

            return expression;
        }
    }
}